#include <glad/glad.h>
#include <GLFW/glfw3.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include <shader_s.h>

void InitGLFW();
bool CreateWindow();
bool InitGLAD();
// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);

// settings 窗口宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

GLFWwindow *window;

int main()
{
    InitGLFW();                      // 初始化GLFW
    bool isCreated = CreateWindow(); // 创建一个窗口对象
    if (!isCreated)
        return -1;
    bool isGLAD = InitGLAD(); // 初始化GLAD，传入加载系统相关opengl函数指针的函数
    if (!isGLAD)
        return -1;

    // 构建和编译着色程序
    Shader ourShader("shader/P1_Basic/04_Textures/texture.vs", "shader/P1_Basic/04_Textures/texture.fs");

    // 设置顶点数据(和缓冲区)并配置顶点属性
    // 1.顶点输入
    float vertices[] = {
        // 位置坐标          // 色值           // 纹理贴图坐标
        0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,   // top right
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left
        -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f   // top left
    };

    unsigned int indices[] = {
        0, 1, 3, // 第一个三角形
        1, 2, 3  // 第二个三角形
    };
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO); // 生成一个VAO对象
    glGenBuffers(1, &VBO);      // 生成一个VBO对象
    glGenBuffers(1, &EBO);      // 生成一个EBO对象

    glBindVertexArray(VAO);
    // 绑定VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);                                        // 绑定VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据复制到缓冲区中

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); // 绑定EBO
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 4.配置位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);
    // 5.   颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // 6.   纹理坐标属性
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // 加载和创建纹理
    unsigned int texture;
    glGenTextures(1, &texture);            // 生成纹理
    glBindTexture(GL_TEXTURE_2D, texture); // 绑定纹理
    // 设置纹理环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // x轴
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // y轴
    // 设置纹理过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // 缩小
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);               // 放大
    // 加载纹理图片,创建纹理和生成多级渐远纹理
    int width, height, nrChannels;
    unsigned char *data = stbi_load("image/04_Textures/container.jpg", &width, &height, &nrChannels, 0);

    if (data)
    {
        // 生成纹理
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        // 生成多级渐远纹理
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    // 释放图像内存
    stbi_image_free(data);

    // 注意这是允许的，调用glVertexAttribPointer将VBO注册为顶点属性的绑定顶点缓冲对象，因此之后我们可以安全地解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // 你可以在之后解除对VAO的绑定，这样其他VAO调用就不会意外地修改这个VAO，但这种情况很少发生。修改其他
    //  VAOs无论如何都需要调用glBindVertexArray，所以当不直接需要时，我们通常不会取消绑定VAOs(也不会取消绑定vbo)。
    glBindVertexArray(0);

    // 取消此调用的注释以绘制线框多边形。
    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // 循环渲染
    while (!glfwWindowShouldClose(window))
    {
        // 输入
        processInput(window);

        // 渲染
        // 清除颜色缓冲
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 绑定纹理
        glBindTexture(GL_TEXTURE_2D, texture);

        // 记得激活着色器
        ourShader.use();
        // 绘制三角形
        glBindVertexArray(VAO);                              // 绑定VAO
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); // 绘制三角形

        // 检查并调用事件，交换缓冲
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 可选:一旦资源超出其用途，就取消分配所有资源:
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);

    // 释放/删除之前的分配的所有资源
    glfwTerminate();
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

void InitGLFW()
{
    // 初始化GLFW
    glfwInit();
    // 配置GLFW  第一个参数代表选项的名称，我们可以从很多以GLFW_开头的枚举值中选择；
    // 第二个参数接受一个整型，用来设置这个选项的值。
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
}
bool CreateWindow()
{
    // 创建一个窗口对象
    window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        // 创建失败，终止程序
        glfwTerminate();
        return false;
    }
    // 将我们窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    // 设置窗口大小改变时的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    return true;
}
bool InitGLAD()
{
    // 初始化GLAD，传入加载系统相关opengl函数指针的函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        // 初始化失败，终止程序
        return false;
    }
    return true;
}

// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    // 设置窗口的维度
    glViewport(0, 0, width, height);
}

// 输入
void processInput(GLFWwindow *window)
{
    // 当用户按下esc键，我们设置window窗口的windowShouldClose属性为true
    // 关闭应用程序
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}
